#include "Scene.h"
#include "Misc.h"
#include "PointLight.h"
#include <cmath>
#include <iostream>
#include <cassert>


namespace RayTrace{

	//int culldirectly=0;
	//int nointersect=0;
//	int lastfindi,lastfindj;
	CScene::CScene(void):shownormals(false),
		scale(1.0f),
		lit(true),
		visible(true),
		totalVerts(0),
		totalFaces(0)
	{


		// Set up the default position
		pos.x = 0.0f;
		pos.y = 0.0f;
		pos.z = 0.0f;
		// Set up the default rotation
		rot.x = 0.0f;
		rot.y = 0.0f;
		rot.z = 0.0f;


		
	}

	CScene::~CScene(void)
	{
		for (size_t i=0;i<mLights.size();++i)
		{
			delete mLights[i];
		}
		mLights.clear();
	}
	void CScene::Init(int w,int h)
	{

		mWidth=w;
		mHeight=h;
		
		SetViewProjSreen();
		SetLights();

	}
	void CScene::Render(void)
	{
		
		mProjectSreen=mCamera.GetScreen();
		mEyepoint=mCamera.GetEyePoint();
			

		Ray tmpRay;
		tmpRay.origin=mEyepoint;


		vector3 cellU=vector3(mProjectSreen.topRight-mProjectSreen.topLeft)/float(mWidth-1);
		vector3 cellV=vector3(mProjectSreen.topLeft-mProjectSreen.bottomLeft)/float(mHeight-1);
		
		int lastperentage=0;
		//Further Improving Cache Performance needed using 32x32 tiles
		for (int i=0;i<mHeight;++i)
		{
			for (int j=0;j<mWidth;++j)
			{
			/*	if (i==255 && j==512)
				{
					int a=lastperentage;
				}*/
				tmpRay.direction=vector3(mProjectSreen.bottomLeft+cellU*float(j)+cellV*float(i)-mEyepoint);
				vector4 tmpColor=RecCalculateColor(tmpRay,3);
			/*	if (tmpColor.x>MMIN &&tmpColor.y>MMIN &&tmpColor.z>MMIN)
				{
					lastfindi=i;
					lastfindj=j;
				}*/
				mRenderBuf[i*mWidth+j]=vector4::LimitInColorRange(tmpColor);
			}
			if (float(i)*100.0f/mHeight>=lastperentage+1)
			{
				system("cls");
				lastperentage=float(i)*100.0f/mHeight;
				std::cout<<"percentage is  "<<lastperentage<<"%"<<std::endl;
			}
			
			
		}
	//	std::cout<<"i ="<<lastfindi<<"j ="<<lastfindj<<std::endl;
	/*	std::cout<<"no intersect exception is"<<nointersect<<std::endl;
		nointersect=0;*/

			
	}

	void CScene::Statistics()
	{
		totalFaces=0;
		totalVerts=0;
		for (int i = 0; i < mObjects.size(); i ++)
		{
			totalFaces += mObjects[i].Faces.size();
			totalVerts += mObjects[i].Vertices.size();
			mObjects[i].GenBoundingBox();
			mObjects[i].GenKdtree();
		}
	}

	void CScene::Load( std::string name )
	{
		//...
		fileLoader.Load(name,this);
		Statistics();
	}

	void CScene::SetLights()
	{
		CPointLight *tmplight=new CPointLight;
		tmplight->SetPos(vector3(-1000,1000,1000));
	//	tmplight->SetPos(mEyepoint);
		//tmplight->SetPos(vector3(-1000.0f,1000.0f,1000.0f));
		tmplight->SetDiffuse(vector4(0.8f,0.8f,0.8f,1.0f));
		tmplight->SetSpecular(vector4(0.8f,0.8f,0.8f,1.0f));
		tmplight->SetAmbient(vector4(0.3f,0.3f,0.3f,1.0f));
		mLights.push_back(tmplight);


		mAbmientColor=vector4(0.5f,0.5f,0.5f,1.0f);
	}

	void CScene::SetViewProjSreen()
	{
	
		mEyepoint=vector3(100,100,100);
		float ratio=float(mWidth)/mHeight;

		mCamera.Set(mEyepoint,vector3(-1,1,-1),vector3(0,0,0),10,ratio,10);
		//mProjectSreen=mCamera.GetScreen();

		/*mProjectSreen.bottomRight=vector3(-10.0f,-10.0f,10.0f*ratio);
		mProjectSreen.topLeft=vector3(-10.0f,10.0f,-10.0f*ratio);
		mProjectSreen.bottomLeft=vector3(-10.0f,-10.0f,-10.0f*ratio);
		mProjectSreen.topRight=vector3(-10.0f,10.0f,10.0f*ratio);*/
		mRenderBuf.resize(mHeight*mWidth);
		for (size_t i=0;i<mRenderBuf.size();++i)
		{
			mRenderBuf[i]=vector4(0.0,0.0,0.0,0.0);
		}
	}	
	vector4 RayTrace::CScene::RecCalculateColor(RayTrace::Ray tmpRay, int rec)
	{
		if (rec==0)
		{
			return vector4(0.0f,0.0f,0.0f,0.0f);
		}
		
	
		CScene::InterSectInf sceneInf;
		bool find=InterSect(tmpRay,sceneInf);
		vector4 color(0.0f,0.0f,0.0f,0.0f);

		if (find)
		{
			const Object& obj=mObjects[sceneInf.objectIndex];
			const Face &hitface=obj.Faces[sceneInf.faceIndex];
			const CMaterial& mat=mMaterials[hitface.MatIndex];

			color=color+mAbmientColor*mat.GetAmbient();
	
			CTriangle triangle=obj.GetTriangleFromFace(hitface);
			vector3 pos=tmpRay.GetPos(sceneInf.dist);//hit position

			CTriangle::IntSectInf inf;
			bool isIntersect=triangle.InArea(pos,inf);
			/*if (!isIntersect)
			{
				isIntersect=triangle.InArea(pos,inf);
				++nointersect;
			}*/
			assert(isIntersect);

			vector3 facenormal=obj.GetNormalFromBarycentric(hitface,inf.barycentric);

			/*if (facenormal.x<0)
			{
				facenormal.x*=-1.0f;
			}
			if (facenormal.y<0)
			{
				facenormal.x*=-1.0f;
			}
			if (facenormal.z<0)
			{
				facenormal.z*=-1.0f;
			}*/
			if (vector3::Dot(tmpRay.direction,facenormal)>0)
			{
				facenormal.x*=-1.0f;
				facenormal.y*=-1.0f;
				facenormal.z*=-1.0f;
			}
			
			/*return vector4(facenormal.Normalize().x,
				facenormal.Normalize().y,
				facenormal.Normalize().z,
							0.0f);*/


			for (size_t i=0;i<mLights.size();++i)
			{
				bool occlusion=false;
				Ray lightray;
				lightray.origin=pos;
				lightray.direction=mLights[i]->GetPos()-pos;
				
				if (vector3::Dot(facenormal,lightray.direction)<0)
				{
					//std::cout<<"back remove"<<std::endl;
					continue;
				}


				CScene::InterSectInf sceneInfAboutLight;
				if (InterSect(lightray,sceneInfAboutLight))
				{
					if (sceneInfAboutLight.dist<1.0f && sceneInfAboutLight.dist>MMIN)
					{
						occlusion=true;
						break;
					}
				}

				if (!occlusion)
				{
					color=color+mLights[i]->CalColor(mEyepoint,pos,facenormal,mat);
				}
			}

			Ray reflectRay;
			reflectRay.origin=pos;
			reflectRay.direction=vector3::Reflect(tmpRay.direction,facenormal);

		/*	Ray refractRay;
			refractRay.origin=pos;
			refractRay.direction=vector3::Refract(tmpRay.direction,facenormal,1.001);*/
			if (rec>1)
			{
				color=0.85f*color+0.15f*RecCalculateColor(reflectRay,rec-1);//+0.2f*RecCalculateColor(refractRay,rec-1);
			}
		}
		return color;
	}


	CScene::FaceIterator::FaceIterator( CScene * Scene ):pScene(Scene),mObjectIndex(0)
	{

	}

	CScene::FaceIterator::FaceIterator():pScene(0),mObjectIndex(0)
	{
	
	}
	void CScene::FaceIterator::first()
	{
		mObjectIndex=0;
		if (!isDone())
		{
			iter.initial(&(pScene->mObjects[mObjectIndex]));
			iter.first();
		}
		
	}
	bool CScene::FaceIterator::isDone()
	{
		if (mObjectIndex>=pScene->mObjects.size())
		{
			return true;
		}
		else
		{
			return false;
		}
	}

	void CScene::FaceIterator::next()
	{
		if (!isDone())
		{
			iter.next();
			if (iter.isDone())
			{
				if (++mObjectIndex<pScene->mObjects.size())
				{
					iter.initial(&(pScene->mObjects[mObjectIndex]));
					iter.first();
				}

			}

		}

	}

	void CScene::FaceIterator::initial(CScene * Scene)
	{
		pScene=Scene;
	}

	const Face& CScene::FaceIterator::face()
	{
		return iter.face();
	}

	size_t CScene::FaceIterator::objectindex()
	{
		return mObjectIndex;
	}

	CScene::FaceIterator::~FaceIterator( void )
	{

	}

	size_t CScene::FaceIterator::materialIndex()
	{
		return iter.materialIndex();
	}

	RayTrace::CTriangle CScene::FaceIterator::triangle()
	{
		return iter.triangle();
	}

	RayTrace::CMaterial CScene::FaceIterator::material()
	{
		return  pScene->mMaterials[iter.materialIndex()];
	}

	RayTrace::vector3 CScene::FaceIterator::GetNormalFromBarycentric( const vector3& barycentric )
	{
		return iter.GetNormalFromBarycentric(barycentric);
	}

	void CScene::FaceIterator::nextIntersect( const Ray& ray )
	{
		if (!isDone())
		{
			iter.next();
			if (iter.isDone())
			{
				++mObjectIndex;
				if (!isDone())
				{
					while(!pScene->mObjects[mObjectIndex].IsIntersect(ray))
					{
						++mObjectIndex;
						if (isDone())
						{
							break;
						}
					}
					if (!isDone())
					{
						iter.initial(&(pScene->mObjects[mObjectIndex]));
						iter.first();
					}

				}


			}

		}
	}	

	void CScene::FaceIterator::firstIntersect( const Ray& ray )
	{
		mObjectIndex=0;
		if (!isDone())
		{

			while(!pScene->mObjects[mObjectIndex].IsIntersect(ray))
			{
				++mObjectIndex;
				if (isDone())
				{
					break;
				}
			}
			if (!isDone())
			{
				iter.initial(&(pScene->mObjects[mObjectIndex]));
				iter.first();
			}

		}

	}
	bool CScene::FaceIterator::operator ==(const FaceIterator& faceit) const
	{
		return (pScene==faceit.pScene&&
			mObjectIndex==faceit.mObjectIndex&&
			iter==faceit.iter);
	}

	void CScene::SetCamera( float eyex,float eyey,float eyez, float upx,float upy,float upz, float centerx,float centery,float centerz, float near, float ratio, float fovy )
	{
		mCamera.Set(vector3(eyex,eyey,eyez),
					vector3(upx,upy,upz),
					vector3(centerx,centery,centerz),
					near,ratio,fovy);
	}

	bool CScene::InterSect( const Ray& ray,InterSectInf & inf )
	{
		inf.dist=MMAX;
		bool find=false;
		for (size_t i=0;i<mObjects.size();++i)
		{
			if (!mObjects[i].IsIntersect(ray))
			{
				continue;
			}

			int tmpfaceindex;

			double t=mObjects[i].Intersect(ray,tmpfaceindex);

			if (t>MMIN && t<inf.dist)//valid distant
			{
				{
					inf.dist=t;
					inf.faceIndex=tmpfaceindex;
					inf.objectIndex=i;
					find=true;
				}
			}
		}
		return find;
	}
}


